/*
 * @(#)JdbcFactReader.java 1.0 2000/12/14
 * 
 * Copyright 2000 Thomas Barnekow. All Rights Reserved.
 * 
 * This software is the proprietary information of Thomas Barnekow. Use is
 * subject to license terms.
 */

package jessx.provider.jdbc;

import java.sql.*;

import jess.*;

import tb.provider.*;
import tb.provider.jdbc.*;
import tb.test.*;
import tb.util.*;

import jessx.provider.*;
import jessx.util.*;

/**
 * A JDBC {@link FactReader}.
 * 
 * @author Thomas Barnekow
 * @version 1.0
 */
public class JdbcFactReader
	extends AbstractJdbcProvider
	implements FactReader {

	public JdbcFactReader(QualifiedName providerName, Connection connection) {
		super(providerName, connection);
	}

	public JdbcFactReader(
		QualifiedName providerName,
		String url,
		String userName,
		String password) {
		super(providerName, url, userName, password);
	}

	public void provide(QualifiedName service, QualifiedName entity) {
		if (service != null && !service.equals(StateProvider.FULL_SELECT)) {
			throw new IllegalArgumentException("Can only provide read-only access");
		}
		getProviderSupport().provide(service, entity);
	}

	/**
	 * This method queries a database table according to the given goal <i>
	 * Fact</i>. It returns a possibly empty {@link jess.ValueVector}of
	 * {@link jess.Fact}s.
	 * 
	 * @param entityName
	 *            table name
	 * @param fact
	 *            Jess fact used to define the query's WHERE clause
	 * @param context
	 *            Jess context
	 */
	public ValueVector select(
		QualifiedName entityName,
		Fact goal,
		Context context)
		throws JessException {
		Debug.println("JdbcFactReader.select: goal = " + goal);

		ValueVector facts = new ValueVector();
		Fact fact = null;
		String factName =
			JessxTools.getFactName(goal).substring(
				FactStorageManager.BACKCHAIN_PREFIX.length());

		Rete engine = context.getEngine();
		Deftemplate dt = goal.getDeftemplate();
		int nslots = dt.getNSlots();
		int index = 0;

		String slotName = null;
		Value slotValue = null;

		StringBuffer query = new StringBuffer(256);
		query.append("SELECT * FROM ").append(entityName.toString("."));

		// Find first non-nil slot value
		for (index = 0; index < nslots; index++) {
			slotName = dt.getSlotName(index);
			slotValue = goal.getSlotValue(slotName);
			if (!slotValue.equals(Funcall.NIL)) {
				appendCondition(
					query.append(" WHERE "),
					slotName,
					dt.getSlotDataType(index),
					slotValue,
					context);
				break;
			}
		}

		// Append next conditions
		for (++index; index < nslots; index++) {
			slotName = dt.getSlotName(index);
			slotValue = goal.getSlotValue(slotName);
			if (!slotValue.equals(Funcall.NIL)) {
				appendCondition(
					query.append(" AND "),
					slotName,
					dt.getSlotDataType(index),
					slotValue,
					context);
			}
		}

		// Execute query
		Connection connection = null;
		Statement stmt = null;
		ResultSet result = null;

		try {
			if ((connection = getConnection()) == null) {
				throw new JessException(
					"JdbcFactReader.select",
					"Not connected",
					0);
			}
		} catch (SQLException ex) {
			throw new JessException(
				"JdbcFactReader.select",
				"SQLException caught",
				ex);
		}

		Debug.println("JdbcFactReader.select: query = " + query.toString());

		try {
			synchronized (connection) {
				stmt = connection.createStatement();
				result = stmt.executeQuery(query.toString());

				while (result.next()) {
					fact = new Fact(factName, engine);

					for (int i = 0; i < dt.getNSlots(); i++) {
						String name = dt.getSlotName(i);
						Value value =
							getValue(name, dt.getSlotDataType(i), result);
						fact.setSlotValue(name, value);
					}

					Debug.println("JdbcFactReader.select: fact = " + fact);

					facts.add(new FactIDValue(fact));
				}
			}
		} catch (SQLException ex) {
			throw new JessException(
				"JdbcFactReader.select",
				"SQLException caught",
				ex);
		} finally {
			try {
				if (result != null)
					result.close();
			} catch (SQLException ex) {
				ex.printStackTrace(System.err);
			}
			try {
				if (stmt != null)
					stmt.close();
			} catch (SQLException ex) {
				ex.printStackTrace(System.err);
			}
			releaseConnection();
		}

		// Over and out...
		return facts;
	}

	/*
	 * Internal helper methods
	 */

	protected Value getValue(String name, int datatype, ResultSet result)
		throws JessException {
		try {
			switch (datatype) {
				case RU.STRING :
					String stringValue = result.getString(name);
					if (!result.wasNull())
						return new Value(stringValue, RU.STRING);
					else
						return Funcall.NIL;
				case RU.INTEGER :
					int intValue = result.getInt(name);
					if (!result.wasNull())
						return new Value(intValue, RU.INTEGER);
					else
						return Funcall.NIL;
				case RU.LONG :
					long longValue = result.getLong(name);
					if (!result.wasNull())
						return new LongValue(longValue);
					else
						return Funcall.NIL;
				case RU.FLOAT :
					double floatValue = result.getDouble(name);
					if (!result.wasNull())
						return new Value(floatValue, RU.FLOAT);
					return Funcall.NIL;
				default :
					Object object = result.getObject(name);
					if (!result.wasNull())
						return new Value(object);
					else
						return Funcall.NIL;
			}
		} catch (SQLException ex) {
			throw new JessException(
				"JdbcFactReader.getValue",
				"Driver threw an exception",
				ex);
		}
	}

	protected StringBuffer appendCondition(
		StringBuffer buffer,
		String name,
		int datatype,
		Value value,
		Context context)
		throws JessException {
		return appendValue(
			buffer.append(name).append('='),
			datatype,
			value,
			context);
	}

	protected StringBuffer appendValue(
		StringBuffer buffer,
		int datatype,
		Value value,
		Context context)
		throws JessException {
		switch (datatype) {
			case RU.ATOM :
			case RU.STRING :
				return buffer.append('\'').append(
					value.stringValue(context)).append(
					'\'');

			case RU.INTEGER :
			case RU.LONG :
			case RU.FLOAT :
				return buffer.append(value.toString());

			case RU.EXTERNAL_ADDRESS :
				return buffer.append(
					value.externalAddressValue(context).toString());

			default :
				throw new IllegalArgumentException(
					"Invalid Jess type: " + datatype);
		}
	}
}
